package net.sevear.chaos.p2p
{
	import com.douban.auth.DBAuthenticate;
	
	import de.popforge.utils.SprintfUtil;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.TimerEvent;
	import flash.net.GroupSpecifier;
	import flash.net.NetConnection;
	import flash.net.NetGroup;
	import flash.net.URLLoader;
	import flash.utils.Timer;
	
	import net.sevear.chaos.douban.shuo.DBShuoSender;
	import net.sevear.chaos.event.ContactEvent;
	import net.sevear.chaos.event.p2p.P2pEvent;
	import net.sevear.chaos.event.p2p.P2pOffLineEvent;
	import net.sevear.chaos.recommend.ContactManager;
	import net.sevear.chaos.utils.DataUtil;
	import net.sevear.chaos.utils.Logger;
	import net.sevear.chaos.view.ChaosLogPanel;
	import net.sevear.chaos.view.recommend.UserGroup;
	import net.sevear.chaos.vo.P2pSiteMsg;
	import net.sevear.chaos.vo.TrackInfo;
	import net.sevear.chaos.vo.UserInfo;
	
	public class P2PFMSiteClient extends EventDispatcher
	{
		private const SERVER:String = "rtmfp://p2p.rtmfp.net/fa5721529849aeba70c6cf56-b983c5cc6bbb/";
		private const DEVKEY:String = "fa5721529849aeba70c6cf56-b983c5cc6bbb";
		private const GROUP_PREIX:String = "net.sevear.chaos.fm";
		
		public static const  CLOSED														:int = -1;
		public static const  LISTEN_OFFLINE												:int = 1;
		public static const  OPEN														:int = 0;
		
		private var _owner:UserInfo;
		private var _groupName:String;
		
		private var _nc:NetConnection;
		private var _netGroup:NetGroup;
		private var _nearID:String;
		private var _groupAddress:String;
		private var groupSpecWithAuthorizations:String;
		private var _connected:Boolean = false;
		
		private var _sitePeerID:String;
		private var _siteAddress:String;
		private var _siteOwnerInfo:UserInfo;
		private var _timer:Timer;
		private var _siteOpen:int = CLOSED;
		
		private var dbshuoSourcer:DBShuoOffLineSource;
		private var lastdbshuoPlaySid:int = -1;
		
		private var bufferList:Array;
		public function P2PFMSiteClient(groupName:String)
		{
			super(null);
			_groupName = groupName;
			bufferList = [];
		}
		
		public function setOwnerInfo(info:UserInfo):void{
			_owner = info;	
		}
		
		public function listen():void{
			if(_connected==false){
				openP2PNet();
			}else{
				dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_SUCCESSFUL));
				if(_siteOpen == CLOSED || _siteOpen == LISTEN_OFFLINE){
					listenOffline();
					connectSite();
				}else if(_siteOpen == OPEN){
					dispatchEvent(new P2pEvent(P2pEvent.SITE_STARTUP));
					if(latestTrack!=null){
						dispatchEvent(new P2pEvent(P2pEvent.NEW_TRACK_ARRIVED,latestTrack));
					}else{
						synchronizeTrack();
					}
				}
			}
		}
		
		public function tryOpenP2p():void{
			openP2PNet();
		}
		
		private function openP2PNet():void{
			if(_connected==false){
				if(_timer!=null && _timer.running) 
					return;
				else{
					startTimer();
					_nc = new NetConnection();
					_nc.addEventListener(NetStatusEvent.NET_STATUS,ncNetStatus);
					_nc.addEventListener(IOErrorEvent.IO_ERROR,onNetIoErr);
					_nc.connect(SERVER+DEVKEY);
				}
			}else{
				dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_SUCCESSFUL));
				connectSite();
			}
		}
		
		public function synchronizeTrack():void{
			if(_siteOpen==OPEN){
				var msg:Object = new Object();
				msg.peerID = _nearID;
				msg.type = P2pSiteMsg.REQUEST_CUR_TRACK;
				postMsg(msg);
			}else{
				if(_connected==false)
					openP2PNet();
				else{
					trace("Current site has not opened");
				}
			}
		}
		
		public function get status():int{
			return _siteOpen;
		}
		
		public function close():void{
			if(_timer!=null && _timer.running) {
				_timer.stop();
			}
			if(_siteOpen == OPEN){
				_siteOpen = CLOSED;
				var msg:Object = new Object();
				msg.type = P2pSiteMsg.LISTENER_DISCONNECT;
				msg.peerID = this._nearID;
				postMsg(msg);
				dispatchEvent(new P2pEvent(P2pEvent.LISTENER_DISCONNECTED,this._sitePeerID));
			}
		}
		
		public function listenOffline():void{
			ChaosLogPanel.getInstance().addBasicInfo("站长暂时不在哦，先听听ta的音乐库吧");
			_siteOpen = LISTEN_OFFLINE;
			if(dbshuoSourcer == null){
				dbshuoSourcer = new DBShuoOffLineSource();
				dbshuoSourcer.addEventListener(P2pOffLineEvent.INIT_FINISHED,onOffLineSourcerInit);
				dbshuoSourcer.addEventListener(P2pOffLineEvent.NEW_LIST_GOTTEN,onGetDBShuoNewList);
				dbshuoSourcer.addEventListener(P2pOffLineEvent.ALL_ITEMS_GOTTEN,onOffLineAllItemsGotten);
				dbshuoSourcer.addEventListener(P2pOffLineEvent.UN_UNAVIABLE,onDBShuoSourcerUnaviable);
				dbshuoSourcer.init(_owner);
			}else{
				getDBShuoList();
			}
		}
		
		private function stopListenningOffLine():void{
			if(dbshuoSourcer!=null){
				dbshuoSourcer.stop();
			}
		}
		
		public function get siteName():String{
			if(_owner!=null) 
				return _owner.dbName+" MHZ";
			else
				return _groupName + " MHZ";
		}
		
		public function get siteID():int{
			return parseInt(_groupName);
		}
		
		public function like(track:TrackInfo):void{
			var msg:Object = new Object();
			msg.peerID = _nearID;
			msg.type = P2pSiteMsg.LIKE_TRACK;
			msg.trackID = track.sid;
			postMsg(msg);
		}
		
		public function nextTrack():TrackInfo{
			var track:TrackInfo =  bufferList.shift() as TrackInfo;
			if(track == null || bufferList.length<1){
				dbshuoSourcer.requestList();
			}
			
			return track;
		}
		
		private function ncNetStatus(e:NetStatusEvent):void{
			switch(e.info.code)
			{
				case "NetConnection.Connect.Success":
					_nearID = _nc.nearID;// or you can use event.target.nearID;
					enterGroup();
					if(_owner==null){
						searchSiteOwnerInfo(parseInt(_groupName));
					}
					break;
				case "NetGroup.Connect.Success":
					_connected = true;
					_groupAddress = _netGroup.convertPeerIDToGroupAddress(_nearID);
					Logger.getInstance().log("P2p Client has connected to ",_groupName);
					connectSite();
					if(_timer!=null && _timer.running) 
						_timer.stop();
					dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_SUCCESSFUL));
					break;
				case "NetGroup.Connect.Failed":
				case "NetGroup.Connect.Rejected":
				case "NetConnection.Connect.AppShutdown":
				case "NetConnection.Connect.Failed"	:
				case "NetConnection.Connect.IdleTimeout":
				case "NetConnection.Connect.InvalidApp":
				case "NetConnection.Connect.Rejected":
					_connected = false;
					Logger.getInstance().logInfo(SprintfUtil.sprintf("Connect p2p site %s failed",_groupName));
					dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_FAILED));
					break;
				case "NetGroup.Posting.Notify":
				case "NetGroup.SendTo.Notify": 
					receivePostNotifyMessage(e.info.message);
					break;
				// FYI: More NetGroup event info codes
				case "NetGroup.Neighbor.Connect":
					connectSite();
					break;
				case "NetGroup.Neighbor.Disconnect":
					onNeighborDisCon(e);
					break;
				case "NetGroup.LocalCoverage.Notify":
				// event.info.message, event.info.from, event.info.fromLocal
				case "NetGroup.MulticastStream.PublishNotify": // event.info.name
				case "NetGroup.MulticastStream.UnpublishNotify": // event.info.name
				case "NetGroup.Replication.Fetch.SendNotify": // event.info.index
				case "NetGroup.Replication.Fetch.Failed": // event.info.index
				case "NetGroup.Replication.Fetch.Result": // event.info.index, event.info.object
				case "NetGroup.Replication.Request": // event.info.index, event.info.requestID
				default:
				{
					break;
				}
			}
		}
		
		/**
		 * Connect with the NETGROUP
		 * */
		private function enterGroup():void
		{
			//			console( className + ".setupGroup()");
			var groupspec:GroupSpecifier = new GroupSpecifier(GROUP_PREIX+_groupName);
			// Allow group members to open channels to server
			groupspec.serverChannelEnabled = true;
			// Allow group members to post
			groupspec.postingEnabled = true;
			
			// Create the group specifi
			groupSpecWithAuthorizations = groupspec.groupspecWithAuthorizations();
			
			// Join the group specified by groupspec
			_netGroup = new NetGroup(_nc, groupSpecWithAuthorizations);
			
			// Register listener for NetGroup NetStatus events
			_netGroup.addEventListener(NetStatusEvent.NET_STATUS, ncNetStatus);
		}
		
		private function connectSite():void{
			var msg:Object = new Object();
			msg.peerID = _nearID;
			msg.type = P2pSiteMsg.LISTENER_WAIT_TO_CONNECT;
			var userInfo:UserInfo = new UserInfo();
			var authenicate:DBAuthenticate = DBAuthenticate.getInstance();
			userInfo.dbUID = authenicate.dbScreenName;
			userInfo.dbName = authenicate.dbName;
			userInfo.dbID = authenicate.dbUID;
			userInfo.dbURL = authenicate.userUrl;
			userInfo.imgUrl = authenicate.imgURL;
			userInfo.isActivate = UserInfo.ACTIVATE_CHAOS;
			msg.data = userInfo;
			postMsg(msg);
		}
		
		private function searchSiteOwnerInfo(id:int):void{
			if(ContactManager.getInstance().hasEventListener(ContactEvent.SEARCH_DB_USER_INFO)==false){
				ContactManager.getInstance().addEventListener(ContactEvent.SEARCH_DB_USER_INFO,onGetSiteOwnerInfo);
				ContactManager.getInstance().addEventListener(ContactEvent.SEARCH_DB_USER_INFO_FAILED,onGetSiteOwnerInfoFailed);
			}
			ContactManager.getInstance().searchDBUserInfo(id);
		}
		
		private function startTimer():void{
			if(_timer == null){
				_timer = new Timer(1000,10);
				_timer.addEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete);
			}else _timer.reset();
			_timer.start();
		}
		
		private function onTimerComplete(e:TimerEvent):void{
			dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_TIMEOUT));
		}
		
		private function onConnectOwener(peerID:String,userInfo:Object):void{
			if(userInfo!=null){
				_timer.stop();
				_sitePeerID = peerID;
				_siteAddress = _netGroup.convertPeerIDToGroupAddress(_sitePeerID);
				_siteOwnerInfo = new UserInfo();
				DataUtil.parseP2pUserInfo(userInfo,_siteOwnerInfo);
				if(_netGroup.addNeighbor(_sitePeerID)){
					_siteOpen = OPEN;
					dispatchEvent(new P2pEvent(P2pEvent.SITE_STARTUP));
					stopListenningOffLine();
					synchronizeTrack();
				}else{
					dispatchEvent(new P2pEvent(P2pEvent.CONNECT_SITE_FAILED));
				}
			}
		}
		
		private function sendClientInfoMsg(type:int):void{
			var msg:Object = new Object();
			msg.peerID = _nearID;
			msg.type = type;
			var userInfo:UserInfo = new UserInfo();
			var authenicate:DBAuthenticate = DBAuthenticate.getInstance();
			userInfo.dbUID = authenicate.dbScreenName;
			userInfo.dbName = authenicate.dbName;
			userInfo.dbID = authenicate.dbUID;
			userInfo.dbURL = authenicate.userUrl;
			userInfo.imgUrl = authenicate.imgURL;
			userInfo.isActivate = UserInfo.ACTIVATE_CHAOS;
			msg.data = userInfo;
			postMsg(msg);
		}
		
		private var _msgOutCount:int = 1;
		private function postMsg(msgObj:Object):void{
			msgObj.sequence = _groupName+(++ _msgOutCount).toString();
			_netGroup.post(msgObj);
		}
		
		private function receivePostNotifyMessage(netGroupMessage:Object):void
		{
			var msg:Object = netGroupMessage as Object;
			switch(msg.type){
				//SITE HAS ADDED THIS CLIENT AS LISTENER
				case P2pSiteMsg.SITE_ADDED_LISTENER:
					stationOpen = OPEN;
					onConnectOwener(msg.peerID as String,msg.data);
					break;
				case P2pSiteMsg.NEW_TRACK:
					stationOpen = OPEN;
					var trackInfo:TrackInfo = new TrackInfo();
					DataUtil.parseP2pTrackInfo(msg.data,trackInfo);
					onGetTrackMsg(trackInfo,msg.sequence as int);
					break;
				//CLIENT IS WAITING FOR THE STARTUP OF THE SITE
				case P2pSiteMsg.SITE_STARTUP:
					stationOpen = OPEN;
					onConnectOwener(msg.peerID as String,msg.data);
					sendClientInfoMsg(P2pSiteMsg.LISTENER_WAIT_TO_CONNECT);
					break;
				case P2pSiteMsg.SITE_CLOSED:
					stationOpen = CLOSED;
					break;
			}
		}
		
		private function set stationOpen(value:int):void{
			if(_siteOpen != value){
				if(value==CLOSED){
					_siteOpen = CLOSED;
					dispatchEvent(new P2pEvent(P2pEvent.SITE_CLOSED,this._sitePeerID));
				}else{
					_siteOpen = OPEN;
					dispatchEvent(new P2pEvent(P2pEvent.SITE_STARTUP));
				}
			}
		}
		
		private var latestTrack:TrackInfo;
		private var latestTrackSequence:int;
		private function onGetTrackMsg(trackInfo:TrackInfo,sequence:int):void{
			if((latestTrack!=null && trackInfo.sid != latestTrack.sid)
			|| latestTrack==null){
				latestTrack = trackInfo;
				latestTrackSequence = sequence;
				dispatchEvent(new P2pEvent(P2pEvent.NEW_TRACK_ARRIVED,trackInfo));
			}else{
				//这个判断是为了防止不断重头播放站长重复广播的歌曲，应该有更好的方法
				if(latestTrackSequence<sequence-1){
					latestTrack = trackInfo;
					latestTrackSequence = sequence;
					dispatchEvent(new P2pEvent(P2pEvent.NEW_TRACK_ARRIVED,trackInfo));
				}
			}
		}
		
		private function onGetSiteOwnerInfo(e:ContactEvent):void{
			if((e.data as UserInfo).dbID == parseInt(_groupName)){
				_owner = e.data as UserInfo;
			}
		}
		
		private function onGetSiteOwnerInfoFailed(e:ContactEvent):void{
			Logger.getInstance().logError("Getting P2P Site owner info failed: "+_groupName);
		}
		
		private function onOffLineSourcerInit(e:P2pOffLineEvent):void{
			getDBShuoList();
		}
		
		private function getDBShuoList():void{
			var shuoList:Array = dbshuoSourcer.getList(lastdbshuoPlaySid);
			if(shuoList!=null && shuoList.length>0){
				for each(var track:TrackInfo in shuoList){
					bufferList.push(track);
				}
				lastdbshuoPlaySid = bufferList[bufferList.length-1].sid as int;
				dispatchEvent(new P2pOffLineEvent(P2pOffLineEvent.NEW_LIST_GOTTEN));
			}else if(bufferList.length<=0){
				dbshuoSourcer.requestList();
			}
		}
		
		private function onGetDBShuoNewList(e:P2pOffLineEvent):void{
			getDBShuoList();
		}
		
		private function onOffLineAllItemsGotten(e:P2pOffLineEvent):void{
			if(bufferList.length==0 && (dbshuoSourcer.getList(-1)==null || dbshuoSourcer.getList(-1).length==0)){
				ChaosLogPanel.getInstance().addBasicInfo("很遗憾，"+_owner.dbName+" MHz 没有找到什么可听的，换个电台试试吧");
			}
		}
		
		private function onNeighborDisCon(e:NetStatusEvent):void{
			var address:String = e.info.neighbor as String;
			if(address == _siteAddress){
				dispatchEvent(new P2pEvent(P2pEvent.SITE_CLOSED,_groupName));
			}
		}
		
		private function onNetIoErr(e:IOErrorEvent):void{
			Logger.getInstance().logError(SprintfUtil.sprintf("Listen p2p site %d IO err",_siteOwnerInfo.dbID));
		}
		
		private function onDBShuoSourcerUnaviable(e:P2pOffLineEvent):void{
			Logger.getInstance().logError("DouBanShuo Sourcer is unaviable");
		}
	}
}